use ane_system::dao::UserId;
use axum::{async_trait, body::Body, http::Request, response::Response};
use fred::{clients::RedisPool, interfaces::KeysInterface as _};
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation};
use loginmanager::{DecodeRequest, LoginInfo, UserMinix};
use sea_orm::{ColumnTrait, DatabaseConnection, DbErr, EntityTrait, ModelTrait, QueryFilter};
use serde::{Deserialize, Serialize};
use tower_sessions::Session;

#[derive(Debug, Serialize, Deserialize, Default)]
struct Claims {
    // aud: String, // Optional. Audience
    exp: u64, // Required (validate_exp defaults to true in validation). Expiration time (as UTC timestamp)
    iat: u64, // Optional. Issued at (as UTC timestamp)
    iss: String, // Optional. Issuer
    nbf: u64, // Optional. Not Before (as UTC timestamp)
    sub: String, // Optional. Subject (whom token refers to)
    uid: String, // Optional. Subject (whom token refers to)
}

#[derive(Clone)]
pub struct JwtManager {
    encoder_key: EncodingKey,
    decoder_key: DecodingKey,
    access_token_expires: u64,
    header: Header,
    validation: Validation,
}

impl JwtManager {
    pub fn new() -> Self {
        let alg = Algorithm::default();
        let mut validation = Validation::new(alg);
        validation.set_audience(&["items"]);
        let header = Header::new(alg);
        Self {
            encoder_key: EncodingKey::from_secret("secret".as_ref()),
            decoder_key: DecodingKey::from_secret("secret".as_ref()),
            access_token_expires: 3600,
            header,
            validation,
        }
    }

    pub fn encode<S: ToString>(&self, uid: S) -> jsonwebtoken::errors::Result<String> {
        let timestamp = jsonwebtoken::get_current_timestamp();
        let my_claims = Claims {
            exp: timestamp + self.access_token_expires,
            iat: timestamp,
            nbf: timestamp,
            uid: uid.to_string(),
            ..Claims::default()
        };
        jsonwebtoken::encode(&self.header, &my_claims, &self.encoder_key)
    }

    pub fn decode(&self, token: &str) -> jsonwebtoken::errors::Result<String> {
        jsonwebtoken::decode::<Claims>(token, &self.decoder_key, &self.validation)
            .map(|x| x.claims.uid)
    }
}

#[test]
fn test_jwt() {
    let jwt = JwtManager::new();
    let id1 = "adsfasdfasd";
    let token = jwt.encode(id1).unwrap();
    let id2 = jwt.decode(&token).unwrap();
    assert_eq!(id1, id2);
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthRole {
    pub id: i32,
    pub key: String,
}

impl AuthRole {
    pub fn new(id: i32, key: String) -> Self {
        Self { id, key }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginUser {
    pub id: i32,
    pub d_id: i32,
    pub name: String,
    pub roles: Vec<AuthRole>,
    /** 数据范围（1：所有数据权限；2：自定义数据权限；3：本部门及以下数据权限；4：本部门数据权限；5：仅本人数据权限） */
    pub data_scope: i8,
    pub status: bool,
}

#[async_trait]
impl UserMinix<axum::http::request::Parts> for LoginUser {
    type Key = i32;

    async fn get_user(id: &Self::Key, req: &mut axum::http::request::Parts) -> Option<Self> {
        req.extensions.get::<AuthManager>().unwrap().get(id).await
    }

    fn get_id(&self) -> &Self::Key {
        &self.id
    }

    fn is_actived(&self) -> bool {
        self.status
    }
}

#[derive(Clone)]
pub struct AuthManager {
    pub jwt: JwtManager,
    redis: Option<RedisPool>,
    exprition: i64,
    conn: DatabaseConnection,
}

impl AuthManager {
    pub fn new(conn: DatabaseConnection) -> Self {
        Self {
            jwt: JwtManager::new(),
            redis: None,
            exprition: 600,
            conn,
        }
    }
    pub fn set_redis(&mut self, redis: RedisPool) -> &Self {
        self.redis = Some(redis);
        self
    }

    pub async fn login(
        &self,
        name: &str,
        password: &str,
    ) -> std::result::Result<Option<(LoginUser, bool)>, DbErr> {
        let s = if let Some(u) = ane_system::entity::sys_user::Entity::find()
            .filter(ane_system::entity::sys_user::Column::Username.eq(name))
            .filter(ane_system::entity::sys_user::Column::DelFlag.eq(false))
            .one(&self.conn)
            .await?
        {
            u
        } else {
            return Ok(None);
        };
        let verity = ane_system::dao::User::check_password(&s.password, password);
        let mut data_scope = 5;
        let roles = s
            .find_linked(ane_system::entity::sys_user_role::UserEnabledRoles)
            .all(&self.conn)
            .await?
            .into_iter()
            .map(|r| {
                data_scope = data_scope.min(r.data_scope);
                AuthRole::new(r.id, r.key)
            })
            .collect::<Vec<_>>();
        Ok(Some((
            LoginUser {
                id: s.user_id,
                d_id: s.dept_id,
                name: s.username,
                status: s.status,
                data_scope,
                roles,
            },
            verity,
        )))
    }

    /// 生产user在redis中的key
    #[inline]
    fn generate_key(id: &UserId) -> String {
        format!("c_user_{id}")
    }

    pub async fn get(&self, id: &UserId) -> Option<LoginUser> {
        if let Some(redis) = &self.redis {
            let key = Self::generate_key(id);
            let s: Option<String> = redis.get(&key).await.unwrap();
            if let Some(st) = s {
                let user = serde_json::from_str::<LoginUser>(&st).ok();
                if user.is_some() {
                    return user;
                }
            }
            let user = self.get_user_by_id(id.clone()).await;
            if user.is_some() {
                let _ = redis
                    .set::<String, _, _>(
                        &key,
                        serde_json::to_string(&user).unwrap(),
                        Some(fred::types::Expiration::EX(self.exprition)),
                        None,
                        false,
                    )
                    .await;
            }
            return user;
        } else {
            return self.get_user_by_id(id.clone()).await;
        }
    }

    /// 清除user保存在redis中的缓存
    pub async fn clean_users_cache(&self, ids: Vec<UserId>) {
        if let Some(redis) = &self.redis {
            for id in ids {
                let key = Self::generate_key(&id);
                redis.del::<String, _>(&key).await;
            }
        }
    }

    /// 获得一个用户信息，如果不存在或软删除则返回None
    ///
    /// status是false也会返回
    async fn get_user_by_id(&self, id: UserId) -> Option<LoginUser> {
        ane_system::entity::sys_user::Entity::find_by_id(id)
            .find_with_linked(ane_system::entity::sys_user_role::UserEnabledRoles)
            .filter(ane_system::entity::sys_user::Column::DelFlag.eq(false))
            .all(&self.conn)
            .await
            .ok()
            .map(|f| f.into_iter().next())
            .flatten()
            .map(|(user, roles)| LoginUser {
                id: user.user_id,
                d_id: user.dept_id,
                name: user.username,
                data_scope: roles.iter().fold(5, |i, x| i.min(x.data_scope)),
                roles: roles
                    .into_iter()
                    .map(|r| AuthRole::new(r.id, r.key))
                    .collect(),
                status: user.status,
            })
    }
}

#[derive(Default, Clone)]
pub struct AneBackend {}

#[async_trait]
impl DecodeRequest<Request<Body>, Response> for AneBackend {
    async fn decode(&self, req: &mut Request<Body>) -> Result<Option<String>, Response> {
        let session = req.extensions().get::<Session>().unwrap().clone();
        let id = session.get("user_id").await.ok().flatten();
        Ok(id)
    }

    async fn update(&self, res: &mut Response) {
        let session = res.extensions().get::<Session>().unwrap().clone();
        let login_info = res.extensions().get::<LoginInfo>().unwrap().clone();
        if let Some(key) = login_info.login_key() {
            // login_info.login_key()
            session.insert("user_id", key).await.unwrap();
            // session.save()
        } else if login_info.is_logout() {
            session.remove_value("user_id").await.unwrap();
            // None
        };
    }
}
